﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using Dycox.ComponentModel;

namespace Dycox.Windows.Forms
{
    [DefaultEvent("ParseValue")]
    public class DataBindingHelper : Component, ISupportInitialize
    {
        public event BindingConvertEventHandler ParseValue;
        public event BindingConvertEventHandler FormatValue;
        public event EventHandler Validating;

        public DataBindingHelper()
        {
        }

        public DataBindingHelper(IContainer container)
            : this()
        {
            if (container == null)
                throw new ArgumentNullException("container");

            container.Add(this);
        }

        public override ISite Site
        {
            get
            {
                return base.Site;
            }
            set
            {
                base.Site = value;
                if (value != null)
                {
                    IDesignerHost host = (IDesignerHost)value.GetService(typeof(IDesignerHost));
                    if (host != null)
                    {
                        ContainerControl root = host.RootComponent as ContainerControl;
                        if (root != null)
                            RootControl = root;
                    }
                }
            }
        }

        private bool _enabled = true;
        [DefaultValue(true), Category("Behavior")]
        public bool Enabled
        {
            get { return _enabled; }
            set { _enabled = value; }
        }

        private ContainerControl _rootControl;
        [DefaultValue(null), Category("Behavior")]
        public ContainerControl RootControl
        {
            get { return _rootControl; }
            set
            {
                if (_rootControl != value)
                {
                    if (_rootControl != null)
                        _rootControl.BindingContextChanged -= OnContainerBindingContextChanged;
                    _rootControl = value;
                    if (_rootControl != null)
                        _rootControl.BindingContextChanged += OnContainerBindingContextChanged;
                    SetBindingManager(_dataSource, _dataMember, true);
                }
            }
        }

        private object _dataSource;
        [AttributeProvider(typeof(IListSource)), DefaultValue(null), Category("Data"), Description("Specifies the data source object which will this helper work on.")]
        public object DataSource
        {
            get { return _dataSource; }
            set
            {
                if (_dataSource != value)
                {
                    if (value == null && Site != null)
                    {
                        IUIService uisvc = (IUIService)Site.GetService(typeof(IUIService));
                        if (uisvc != null)
                        {
                            if (uisvc.ShowMessage("Setting the DataSource property to null will cause this helper not work, continue anywhere?", "CAUTION", MessageBoxButtons.YesNo)
                                 == DialogResult.No)
                                return;
                        }
                    }
                    SetBindingManager(value, _dataMember, false);
                }
            }
        }

        private string _dataMember;
        [
        Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor)),
        DefaultValue(null), Category("Data")
        ]
        public string DataMember
        {
            get { return _dataMember; }
            set
            {
                if (_dataMember != value)
                {
                    SetBindingManager(_dataSource, value, false);
                }
            }
        }

        private bool _validationEnabled;
        [DefaultValue(false), Category("Behavior")]
        public bool ValidationEnabled
        {
            get { return _validationEnabled; }
            set
            {
                if (_validationEnabled != value)
                {
                    _validationEnabled = value;

                    if (value && _errorProvider == null && this.DesignMode)
                    {
                        IUIService uisvc = (IUIService)GetService(typeof(IUIService));
                        IReferenceService refsvc = (IReferenceService)GetService(typeof(IReferenceService));
                        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
                        if (uisvc != null && refsvc != null && host != null)
                        {
                            string msg = "We need a ErrorProvider component to show validation messages. Do you want the designer choose or create one for you?";
                            if (uisvc.ShowMessage(msg, null, MessageBoxButtons.YesNo) == DialogResult.Yes)
                            {
                                object[] array = refsvc.GetReferences(typeof(ErrorProvider));
                                if (array.Length > 0)
                                {
                                    foreach (ErrorProvider ep in array)
                                    {
                                        if (ep.DataSource == _dataSource)
                                        {
                                            this.ErrorProvider = ep;
                                            break;
                                        }
                                    }
                                    if (_errorProvider == null)
                                        this.ErrorProvider = (ErrorProvider)array[0];
                                }
                                else
                                {
                                    ErrorProvider ep = (ErrorProvider)host.CreateComponent(typeof(ErrorProvider));
                                    ep.ContainerControl = this._rootControl;
                                    this.ErrorProvider = ep;
                                }
                            }
                        }
                    }
                }
            }
        }

        void DesignerComponentRemoved(object sender, ComponentEventArgs e)
        {
            if (e.Component == this._errorProvider)
            {
                this.ErrorProvider = null;
                this.ValidationEnabled = false;
                ((IComponentChangeService)sender).ComponentRemoved -= DesignerComponentRemoved;
            }
        }

        private ErrorProvider _errorProvider;
        [DefaultValue(null), Category("Appearance"), Description("When ValidationEnabled property set to true, DataBindingHelper will use this ErrorProvider to show error information.")]
        public ErrorProvider ErrorProvider
        {
            get { return _errorProvider; }
            set
            {
                if (_errorProvider != value)
                {
                    if (value != null && this.DesignMode)
                    {
                        IComponentChangeService ccsvc = (IComponentChangeService)GetService(typeof(IComponentChangeService));
                        if (ccsvc != null)
                        {
                            ccsvc.ComponentRemoved -= DesignerComponentRemoved;
                            ccsvc.ComponentRemoved += DesignerComponentRemoved;
                        }
                    }
                    _errorProvider = value;
                    if (_errorProvider != null && DesignMode)
                    {
                        //if (value.DataSource == null && this._dataSource != null)
                        //{
                        //    value.DataSource = _dataSource;
                        //    value.DataMember = _dataMember;
                        //}
                        //else
                        if (this._dataSource == null && value.DataSource != null)
                        {
                            DataSource = value.DataSource;
                            DataMember = value.DataMember;
                        }

                    }
                }
            }
        }


        private void OnContainerBindingContextChanged(object sender, EventArgs e)
        {
            SetBindingManager(_dataSource, _dataMember, true);
        }

        private BindingManagerBase GetBindingManager()
        {
            BindingManagerBase bmb;
            if (_dataSource != null && _rootControl != null)
            {
                try
                {
                    bmb = _rootControl.BindingContext[_dataSource, _dataMember];
                }
                catch
                {
                    bmb = null;
                }
            }
            else
                bmb = null;

            return bmb;
        }

        void OnManagerBindingComplete(object sender, BindingCompleteEventArgs e)
        {
            if (e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate && _enabled && _validationEnabled)
            {
                if (_bindingManager != null)
                    PerformValidation(false);
            }
        }

        protected void OnValidating(EventArgs e)
        {
            if (Validating != null)
                Validating(this, e);
        }

        public MessageSet PerformValidation(bool flush)
        {
            if (_bindingManager == null)
                throw new InvalidOperationException("Component has not been initialized yet.");

            if (_validationEnabled)
            {

                // ensures all values written into the data-bound object.
                if (flush) WriteValues();

                OnValidating(EventArgs.Empty);

                var msgSet = Validation.ValidationManager.Validate(_bindingManager.Current, false);

                if (_errorProvider != null)
                {
                    Dictionary<Control, StringBuilder> dict = new Dictionary<Control, StringBuilder>();
                    Binding[] array = _bindingManager.Bindings.Cast<Binding>().ToArray();

                    if (msgSet.Level == ComponentModel.MessageKind.Error)
                    {
                        foreach (Binding binding in array)
                        {
                            foreach (Validation.ValidationMessageEntry entry in msgSet)
                            {
                                if (entry.Property.Name == binding.BindingMemberInfo.BindingField && entry.Kind == ComponentModel.MessageKind.Error)
                                {
                                    StringBuilder sb;
                                    if (!dict.TryGetValue(binding.Control, out sb))
                                    {
                                        sb = new StringBuilder();
                                        dict.Add(binding.Control, sb);
                                    }
                                    if (sb.Length > 0) sb.AppendLine();
                                    sb.Append(entry.Text);
                                }
                            }
                        }
                    }

                    foreach (Control control in array.Select(o => o.Control).Distinct())
                    {
                        StringBuilder sb;

                        if (dict.TryGetValue(control, out sb))
                            _errorProvider.SetError(control, sb.ToString());
                        else
                            _errorProvider.SetError(control, null);
                    }
                }

                return msgSet;
            }
            else
                throw new InvalidOperationException("Validation is not enabled. Set ValidationEnabled property to true to solve this problem.");

        }

        public void WriteValues()
        {

            foreach (Binding binding in _bindingManager.Bindings)
            {
                Control control = binding.Control;

                if (control.Focused || control is ContainerControl && ((ContainerControl)control).ActiveControl != null )
                {
                    binding.WriteValue();
                    break;
                }
            }

        }

        BindingManagerBase _bindingManager;

        private void SetBindingManager(object dataSource, string dataMember, bool forced)
        {
            if (_dataSource != dataSource || _dataMember != dataMember || forced)
            {
                _dataSource = dataSource;
                _dataMember = dataMember;

                if (!_initializing)
                {
                    UnwireEvents(_bindingManager);
                    if (_rootControl != null && _rootControl.BindingContext != null && dataSource != null)
                        _bindingManager = _rootControl.BindingContext[dataSource, dataMember];
                    else
                        _bindingManager = null;
                    WireEvents(_bindingManager);
                    //SyncBindings(_bindingManager);
                }
                else
                    _pendingSync = true;
            }
        }

        List<BindingManagerBase> _wiredManagers;

        private void WireEvents(BindingManagerBase bmb)
        {
            if (bmb != null)
            {
                if (_wiredManagers == null || !_wiredManagers.Contains(bmb))
                {
                    bmb.BindingComplete += OnManagerBindingComplete;
                    bmb.Bindings.CollectionChanged += OnManagerBindingsChanged;

                    if (_wiredManagers == null)
                        _wiredManagers = new List<BindingManagerBase>();
                    _wiredManagers.Add(bmb);
                }
            }
        }

        private void OnManagerBindingsChanged(object sender, CollectionChangeEventArgs e)
        {
            if (!_initializing)
            {
                Binding binding = (Binding)e.Element;
                //Console.WriteLine("{0} binding {1}", e.Action, binding.BindingMemberInfo.BindingField);
                if (e.Action == CollectionChangeAction.Add)
                    WireEvents(binding);
                else if (e.Action == CollectionChangeAction.Remove)
                    UnwireEvents(binding);
            }
        }

        private void WireEvents(Binding binding)
        {
            UnwireEvents(binding);
            if (binding != null)
            {
                binding.Format += OnBindingFormat;
                binding.Parse += OnBindingParse;
            }
        }

        private void UnwireEvents(Binding binding)
        {
            if (binding != null)
            {
                binding.Format -= OnBindingFormat;
                binding.Parse -= OnBindingParse;
            }
        }

        private void UnwireEvents(BindingManagerBase bmb)
        {
            if (bmb != null)
            {
                bmb.BindingComplete -= OnManagerBindingComplete;
                bmb.Bindings.CollectionChanged -= OnManagerBindingsChanged;

                if (_wiredManagers != null)
                    _wiredManagers.Remove(bmb);
            }
        }

        public void Flush()
        {
            _bindingManager.SuspendBinding();
            foreach (Binding binding in _bindingManager.Bindings)
            {
                binding.WriteValue();
            }
            _bindingManager.ResumeBinding();
        }

        //List<Binding> _bindings;

//        private void SyncBindings(BindingManagerBase bmb)
//        {

//            Binding[] array, adds, removes;
//            if (bmb != null)
//            {
//                array = bmb.Bindings.Cast<Binding>().ToArray();
//            }
//            else
//                array = new Binding[0];

//            removes = _bindings.Except(array).ToArray();
//            adds = array.Except(_bindings).ToArray();

//            foreach (Binding binding in adds)
//            {
//                binding.Format += OnBindingFormat;
//                binding.Parse += OnBindingParse;
//                _bindings.Add(binding);
//            }

//            foreach (Binding binding in removes)
//            {
//                if (_bindings.Remove(binding))
//                {
//                    binding.Format -= OnBindingFormat;
//                    binding.Parse -= OnBindingParse;
//                }
//            }


//#if TRACE
//            System.Diagnostics.Trace.WriteLine(
//                String.Format("Total: {0}, Adds: {1}, Removes: {2}", array.Length, adds.Length, removes.Length));
//#endif
//        }

        private void OnBindingFormat(object sender, ConvertEventArgs e)
        {
            if (_enabled)
            {
                BindingConvertEventArgs bce = new BindingConvertEventArgs((Binding)sender, e.Value, e.DesiredType);
                OnFormatValue(bce);
                if (!bce.Handled)
                    FormatValueCore(bce);
                e.Value = bce.Value;
            }
        }

        protected virtual void OnFormatValue(BindingConvertEventArgs e)
        {
            if (FormatValue != null)
                FormatValue(this, e);
        }

        private void OnBindingParse(object sender, ConvertEventArgs e)
        {
            if (_enabled)
            {
                BindingConvertEventArgs bce = new BindingConvertEventArgs((Binding)sender, e.Value, e.DesiredType);
                OnParseValue(bce);
                if (!bce.Handled)
                    ParseValueCore(bce);
                e.Value = bce.Value;
            }
        }

        protected virtual void OnParseValue(BindingConvertEventArgs e)
        {
            if (ParseValue != null)
                ParseValue(this, e);
        }

        bool _initializing, _pendingSync;

        void ISupportInitialize.BeginInit()
        {
            _initializing = true;
        }

        void EndInit()
        {
            _initializing = false;
            if (_pendingSync)
                SetBindingManager(_dataSource, _dataMember, true);
        }

        void ISupportInitialize.EndInit()
        {
            ISupportInitializeNotification obj = _dataSource as ISupportInitializeNotification;

            if (obj != null && !obj.IsInitialized)
            {
                obj.Initialized += DataSourceInitialized;
            }
            else
            {
                EndInit();
            }
        }

        void DataSourceInitialized(object sender, EventArgs e)
        {
            ISupportInitializeNotification obj = (ISupportInitializeNotification)sender;
            if (obj != null)
                obj.Initialized -= DataSourceInitialized;
            EndInit();
        }

        private void FormatValueCore(BindingConvertEventArgs e)
        {
            IValueResolvingStrategy stragegy = GetResolvingStrategy(e);
            if (stragegy != null)
                stragegy.FormatValue(e);
        }

        private void ParseValueCore(BindingConvertEventArgs e)
        {
            IValueResolvingStrategy stragegy = GetResolvingStrategy(e);
            if (stragegy != null)
                stragegy.ParseValue(e);
        }

        static List<IValueResolvingStrategy> ResolvingStrategies;

        private static void EnsureStrategies()
        {
            if (ResolvingStrategies == null)
            {
                ResolvingStrategies = new List<IValueResolvingStrategy>();
                ResolvingStrategies.Add(new NullValueResolvingStrategy());
                ResolvingStrategies.Add(new DateTimePickerResolvingStrategy());
                ResolvingStrategies.Add(new EnumResolvingStragegy());
                ResolvingStrategies.Add(new StringPropertyResolvingStrategy());
            }
        }

        private static IValueResolvingStrategy GetResolvingStrategy(BindingConvertEventArgs bce)
        {
            EnsureStrategies();
            return ResolvingStrategies.FirstOrDefault(o => o.CanResolve(bce));
        }

        public void Fix(TabControl tabControl)
        {
            TabPage selected = tabControl.SelectedTab;

            foreach (TabPage tab in tabControl.TabPages)
            {
                if (tab != selected)
                    tabControl.SelectTab(tab);
            }

            tabControl.SelectTab(selected);
        }


    }
}
